import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * trieda na zistenie referencii s datami zo vstupneho texu so vstupnym formatom
 * @author cerko
 *
 */
public class RefDecoder {
	
	private File inputTex;
	private File inputFormat;
	private String lineBreak;
	
	private Map<String, List<InputFormatData>> inputFormats;
	private Map<String, Data> refsInBib;
	
	
	public RefDecoder(){
		
	}
	
	/**
	 * pokusi sa nacitat referencie do podoby akej su v bibloaderi pomcou vstupneho formatu
	 * @param inputTex - vstupny tex
	 * @param inputFormat - format v akom je texdokument
	 * @return - mapa meno - data
	 */
	public Map<String, Data> referencesToBib(File inputTex, File inputFormat){
		this.inputTex = inputTex;
		this.inputFormat = inputFormat;
		inputFormats = new HashMap<String, List<InputFormatData>>();
		this.lineBreak = System.getProperty("line.separator");
		
		loadFormat();
		return decodeTexRefs(inputTex);
	}
	
	/**
	 * nacita vstupny format do mulktimapy typ - zoznam formatov
	 */
	private void loadFormat(){
		Scanner scanner = null;
		try {
			scanner = new Scanner(inputFormat);
			scanner.useDelimiter("@@");
			while(scanner.hasNext()){
				scanBlock(scanner.next());
			}
			scanner.close();
		} catch (FileNotFoundException e) {
			// TODO do logu
			e.printStackTrace();
		}
	}
	
	/**
	 * nacita typ pre ktory je format definovany a nacita aj definicie pomenovanych regex group
	 * @param block - blok textu pre tuto definiciu
	 */
	public void scanBlock(String block){
		Matcher typeMatcher = Pattern.compile("([a-z])*(?=\\{)").matcher(block);
		String type = null;
		if (typeMatcher.find()){
			type = typeMatcher.group();
		}
		else {
			//TODO do logu
			return;
		}
		
		
		/*
		Scanner scanner = new Scanner(block);
		scanner.useDelimiter(lineBreak);
		
		String firstLine = scanner.next();
		*/
		
		Matcher matcher = Pattern.compile("(@[a-zA-Z0-9]*=[^@" + Pattern.quote(lineBreak) + "]*)", Pattern.DOTALL).matcher(block);
		
		List<InputFormatData> typesInputFormats = inputFormats.get(type);
		if (typesInputFormats == null){
			typesInputFormats = new LinkedList<InputFormatData>();
		}
		InputFormatData currentFormatSettings = new InputFormatData();
		int lastEndIndex = -1;
		while(matcher.find()){
			currentFormatSettings.addCapturingGroupPattern(scanCaptureGroupSetting(matcher.group()));
			lastEndIndex = matcher.end();
		}
		//System.out.println(lastEndIndex);
		//System.out.println(block.charAt(lastEndIndex - 1));
		//System.out.println(block.substring(lastEndIndex));
		matcher.reset();
		matcher.region(lastEndIndex, block.length() - 1);
		matcher.usePattern(Pattern.compile("[ \\n\\r]*\\}[ \\n\\r]*"));
		String inputFormatPattern = null;
		if (matcher.find()){
			//System.out.println(matcher.end());
			//System.out.println(block.substring(matcher.end()));
			//inputFormatPattern = block.substring(matcher.end());
			currentFormatSettings.setFormatPattern(buildInputFormatPattern(currentFormatSettings, block.substring(matcher.end())));
			typesInputFormats.add(currentFormatSettings);
			inputFormats.put(type, typesInputFormats);
		}
		
		
	}
	
	/**
	 * vytvori cely regex na matchovanie
	 * @param formatSettings - nastavenie named groups
	 * @param block - blok textu
	 * @return - cely regex s vyhodnotenymi premennymi
	 */
	public String buildInputFormatPattern(InputFormatData formatSettings, String block){
		String result = "";
		Matcher matcher = Pattern.compile("(@[a-z]*(?=[^a-z]))|([^@]+)", Pattern.DOTALL).matcher(block);
		while(matcher.find()){
			//System.out.println(matcher.group());
			if (matcher.group().charAt(0) == '@'){
				String key = matcher.group().substring(1);
				Map<String, String> capturingGroupPatterns = formatSettings.getCapturingGroupPatterns();
				result += capturingGroupPatterns.get(key);
			}
			else {
				result += matcher.group();
			}
		}
		return result;
	}
	
	/**
	 * nacita kluc a definiciu named groupy
	 * @param captureGroupSetting - dvojica v texte
	 * @return - entry typu key - definicia
	 */
	public AbstractMap.SimpleEntry<String, String> scanCaptureGroupSetting(String captureGroupSetting){
		Matcher matcher = Pattern.compile("(@[a-zA-Z0-9]*(?==))").matcher(captureGroupSetting);
		String captureGroupID = null;
		if (matcher.find()){
			captureGroupID = matcher.group().replace("@", "");
		}
		matcher = Pattern.compile("(?<==).*(?=,)").matcher(captureGroupSetting);
		String captureGroupPattern = null;
		if (matcher.find()){
			captureGroupPattern = matcher.group();
		}
		return new AbstractMap.SimpleEntry<String, String>(captureGroupID, captureGroupPattern);
	}
	
	/**
	 * pokusi sa nacitat referencie zo vstupneho texu do bibloaderovskej podoby pomocou nahrateho formatu
	 * @param inputTex - vstupny tex
	 * @return - menoreferencie - data
	 */
	public Map<String, Data> decodeTexRefs(File inputTex){
		BufferedReader reader = null;
		String input = "";
		try {
			reader = new BufferedReader(new FileReader(inputTex));
			String line = "";
			while ((line = reader.readLine()) != null){
				input += line + System.getProperty("line.separator");
			}
			reader.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if (input != ""){
			//bibliography stripping
			Matcher matcher = Pattern.compile("(?<=\\\\begin\\{thebibliography}).*(\\\\end\\{thebibliography})", Pattern.DOTALL).matcher(input);
			if (matcher.find()){
				String bibliography = matcher.group();
				if(Frontend.DEBUG){
					System.out.println(bibliography);
				}
				matcher = Pattern.compile("(\\\\bibitem\\{[a-zA-Z0-9]*}).*?(?=(\\\\bibitem|\\\\end\\{thebibliography}))", Pattern.DOTALL).matcher(bibliography);
				ArrayList<String> bibItems = new ArrayList<String>();
				while (matcher.find()){
					bibItems.add(matcher.group());
					if (Frontend.DEBUG){
						System.out.println(matcher.group());
					}
				}
				Map<String, Data> res = new TreeMap<String, Data>(String.CASE_INSENSITIVE_ORDER);
				for (String ss : bibItems){
					matcher = Pattern.compile("(?<=(\\\\bibitem\\{))([a-zA-z0-9]*)}").matcher(ss);
					if (matcher.find()){
						String name = matcher.group(2); 
						Data data = refToData(name, ss);
						if (data != null) {
							res.put(name, data);
						}
						else {
							//TODO do logu ze nevedel najst format
							System.out.println("Unable to match a format for :" + name);
						}
						
					}
					
				}
				return res;
			}
		}
		return null;
	}
	
	/**
	 * pokusi sa matchnut jednotlivym referenciam nejaky pattern ak ho matchne tak vracia data
	 * @param name - meno referencie
	 * @param _ref - cela referencia v texe okrem /bibitem riadka
	 * @return - data ak sa podari matchnut format inak null
	 */
	public Data refToData(String name, String _ref){
		for (String type : inputFormats.keySet()){
			for (InputFormatData inputFormat : inputFormats.get(type)){
				String ref = _ref.substring(_ref.indexOf(lineBreak) + 2);
				Matcher matcher = Pattern.compile(inputFormat.getFormatPattern()).matcher(ref);
				
				if (matcher.matches()){
					if (Frontend.DEBUG){
						System.out.println(name);
						System.out.println(matcher.group());
					}
					
					HashMap<String, String> refsData = new HashMap<String, String>();
					for (String key : inputFormat.getCapturingGroupPatterns().keySet()){
						refsData.put(key, matcher.group(key));
					}
					return new Data(type, refsData);
					
				}
				else {
					//TODO do logu
				}
			}
		}
		//TODO do logu ze nenasiel ziaden match
		return null;
	}
	
}
